#include "U2GLESFBORenderTexture.h"

#include "U2GLESPixelUtil.h"
#include "U2LogManager.h"
#include "U2Root.h"
#include "U2GLESHardwarePixelBuffer.h"
/**********************@@@@@@@@@@@@@@@@@@@@@
#include "OgreGLESFBOMultiRenderTarget.h"
*/


U2EG_NAMESPACE_USING


//-----------------------------------------------------------------------------    
GLESFBORenderTexture::GLESFBORenderTexture(GLESFBOManager *manager, const U2String &name,
    const U2GLESSurfaceDesc &target, bool writeGamma, u2uint fsaa)
: U2GLESRenderTexture(name, target, writeGamma, fsaa)
, mFB(manager, fsaa)
{
    // Bind target to surface 0 and initialise
    mFB.bindSurface(0, target);
    GL_CHECK_ERROR;
    // Get attributes
    mWidth = mFB.getWidth();
    mHeight = mFB.getHeight();
}

void GLESFBORenderTexture::getCustomAttribute(const U2String& name, void* pData)
{
    if(name=="FBO")
    {
        *static_cast<GLESFrameBufferObject **>(pData) = &mFB;
    }
}

void GLESFBORenderTexture::swapBuffers(bool waitForVSync)
{
	mFB.swapBuffers();
}

//-----------------------------------------------------------------------------
bool GLESFBORenderTexture::attachDepthBuffer( U2DepthBuffer *depthBuffer )
{
	bool result;
	if( (result = U2GLESRenderTexture::attachDepthBuffer( depthBuffer )) )
		mFB.attachDepthBuffer( depthBuffer );

	return result;
}
//-----------------------------------------------------------------------------
void GLESFBORenderTexture::detachDepthBuffer()
{
	mFB.detachDepthBuffer();
	U2GLESRenderTexture::detachDepthBuffer();
}
//-----------------------------------------------------------------------------
void GLESFBORenderTexture::_detachDepthBuffer()
{
	mFB.detachDepthBuffer();
	U2GLESRenderTexture::_detachDepthBuffer();
}

/// Size of probe texture
#define PROBE_SIZE 16

/// Stencil and depth formats to be tried
static const GLenum stencilFormats[] =
{
    GL_NONE,                    // No stencil
    GL_STENCIL_INDEX8_OES
};
static const size_t stencilBits[] =
{
    0, 8
};
#define STENCILFORMAT_COUNT (sizeof(stencilFormats) / sizeof(GLenum))

static const GLenum depthFormats[] =
{
    GL_NONE,
    GL_DEPTH_COMPONENT16_OES,
    GL_DEPTH_COMPONENT24_OES,   // Prefer 24 bit depth
    GL_DEPTH24_STENCIL8_OES     // packed depth / stencil
};
static const size_t depthBits[] =
{
    0, 16, 24, 24
};
#define DEPTHFORMAT_COUNT (sizeof(depthFormats)/sizeof(GLenum))

GLESFBOManager::GLESFBOManager() 
	: mTempFBO(0)
{
    detectFBOFormats();
    
    glGenFramebuffersOES(1, &mTempFBO);
    GL_CHECK_ERROR;
}

GLESFBOManager::~GLESFBOManager()
{
	if(!mRenderBufferMap.empty())
	{
		U2LogManager::getSingleton().logMessage("GL ES: Warning! GLESFBOManager destructor called, but not all renderbuffers were released.");
	}
    
    glDeleteFramebuffersOES(1, &mTempFBO);      
    GL_CHECK_ERROR;
}

/** Try a certain FBO format, and return the status. Also sets mDepthRB and mStencilRB.
    @returns true    if this combo is supported
             false   if this combo is not supported
*/
GLuint GLESFBOManager::_tryFormat(GLenum depthFormat, GLenum stencilFormat)
{
    GLuint status, depthRB = 0, stencilRB = 0;

    if(depthFormat != GL_NONE)
    {
        /// Generate depth renderbuffer
        glGenRenderbuffersOES(1, &depthRB);

        /// Bind it to FBO
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRB);
        
        /// Allocate storage for depth buffer
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, depthFormat,
                            PROBE_SIZE, PROBE_SIZE);
        
        /// Attach depth
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
                                GL_RENDERBUFFER_OES, depthRB);
    }

    // Stencil buffers aren't available on iPhone
    if(stencilFormat != GL_NONE)
    {
        /// Generate stencil renderbuffer
        glGenRenderbuffersOES(1, &stencilRB);
        
        /// Bind it to FBO
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, stencilRB);

        /// Allocate storage for stencil buffer
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, stencilFormat,
                            PROBE_SIZE, PROBE_SIZE); 

        /// Attach stencil
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES,
                        GL_RENDERBUFFER_OES, stencilRB);
    }

    status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);

    /// If status is negative, clean up
    // Detach and destroy
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);

    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);

    if (depthRB)
        glDeleteRenderbuffersOES(1, &depthRB);

    if (stencilRB)
        glDeleteRenderbuffersOES(1, &stencilRB);
    
    return status == GL_FRAMEBUFFER_COMPLETE_OES;
}

/** Try a certain packed depth/stencil format, and return the status.
    @returns true    if this combo is supported
             false   if this combo is not supported
*/
bool GLESFBOManager::_tryPackedFormat(GLenum packedFormat)
{
    GLuint packedRB;

    /// Generate renderbuffer
    glGenRenderbuffersOES(1, &packedRB);

    /// Bind it to FBO
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, packedRB);

    /// Allocate storage for buffer
    glRenderbufferStorageOES(GL_RENDERBUFFER_OES, packedFormat, PROBE_SIZE, PROBE_SIZE);

    /// Attach depth
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
        GL_RENDERBUFFER_OES, packedRB);

    /// Attach stencil
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES,
        GL_RENDERBUFFER_OES, packedRB);

    GLuint status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);

    /// Detach and destroy
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0);
    glDeleteRenderbuffersOES(1, &packedRB);

    return status == GL_FRAMEBUFFER_COMPLETE_OES;
}

/** Detect which internal formats are allowed as RTT
    Also detect what combinations of stencil and depth are allowed with this internal
    format.
*/
void GLESFBOManager::detectFBOFormats()
{
    // Try all formats, and report which ones work as target
    GLuint fb, tid;
    GLenum target = GL_TEXTURE_2D;

    for(size_t x=0; x<PF_COUNT; ++x)
    {
        mProps[x].valid = false;

		// Fetch GL format token
		GLenum fmt = U2GLESPixelUtil::getGLInternalFormat((PixelFormat)x);
        if(fmt == GL_NONE && x!=0)
            continue;

		// No test for compressed formats
		if(U2PixelUtil::isCompressed((PixelFormat)x))
			continue;

        // Create and attach framebuffer
        glGenFramebuffersOES(1, &fb);
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, fb);
        if (fmt!=GL_NONE)
        {
			// Create and attach texture
			glGenTextures(1, &tid);
			glBindTexture(target, tid);
			
            // Set some default parameters
            glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
            glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                        
			glTexImage2D(target, 0, fmt, PROBE_SIZE, PROBE_SIZE, 0, fmt, GL_UNSIGNED_BYTE, 0);
			glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
                            target, tid, 0);
        }

        // Check status
        GLuint status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);

		// Ignore status in case of fmt==GL_NONE, because no implementation will accept
		// a buffer without *any* attachment. Buffers with only stencil and depth attachment
		// might still be supported, so we must continue probing.
        if(fmt == GL_NONE || status == GL_FRAMEBUFFER_COMPLETE_OES)
        {
            mProps[x].valid = true;
			U2StringUtil::U2StrStreamType str;
			str << "FBO " << U2PixelUtil::getFormatName((PixelFormat)x) 
				<< " depth/stencil support: ";

            // For each depth/stencil formats
            for (size_t depth = 0; depth < DEPTHFORMAT_COUNT; ++depth)
            {
                if (depthFormats[depth] != GL_DEPTH24_STENCIL8_OES)
                {
                    // General depth/stencil combination

                    for (size_t stencil = 0; stencil < STENCILFORMAT_COUNT; ++stencil)
                    {
                        //U2StringUtil::U2StrStreamType l;
                        //l << "Trying " << U2PixelUtil::getFormatName((PixelFormat)x) 
                        //	<< " D" << depthBits[depth] 
                        //	<< "S" << stencilBits[stencil];
                        //U2LogManager::getSingleton().logMessage(l.str());

                        if (_tryFormat(depthFormats[depth], stencilFormats[stencil]))
                        {
                            /// Add mode to allowed modes
                            str << "D" << depthBits[depth] << "S" << stencilBits[stencil] << " ";
                            FormatProperties::Mode mode;
                            mode.depth = depth;
                            mode.stencil = stencil;
                            mProps[x].modes.push_back(mode);
                        }
                    }
                }
                else
                {
                    // Packed depth/stencil format
                    if (_tryPackedFormat(depthFormats[depth]))
                    {
                        /// Add mode to allowed modes
                        str << "Packed-D" << depthBits[depth] << "S" << 8 << " ";
                        FormatProperties::Mode mode;
                        mode.depth = depth;
                        mode.stencil = 0;   // unuse
                        mProps[x].modes.push_back(mode);
                    }
                }
            }
            U2LogManager::getSingleton().logMessage(str.str());
        }

        // Delete texture and framebuffer
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
        glDeleteFramebuffersOES(1, &fb);
		
        if (fmt!=GL_NONE)
            glDeleteTextures(1, &tid);
    }

	U2String fmtstring;
    for(size_t x=0; x<PF_COUNT; ++x)
    {
        if(mProps[x].valid)
            fmtstring += U2PixelUtil::getFormatName((PixelFormat)x)+" ";
    }
    U2LogManager::getSingleton().logMessage("[GLES] : Valid FBO targets " + fmtstring);
}

void GLESFBOManager::getBestDepthStencil(GLenum internalFormat, GLenum *depthFormat, GLenum *stencilFormat)
{
    const FormatProperties &props = mProps[internalFormat];
    /// Decide what stencil and depth formats to use
    /// [best supported for internal format]
    size_t bestmode = 0;
    int bestscore = -1;
    for(size_t mode = 0; mode < props.modes.size(); mode++)
    {
        int desirability = 0;
        /// Find most desirable mode
        /// desirability == 0            if no depth, no stencil
        /// desirability == 1000...2000  if no depth, stencil
        /// desirability == 2000...3000  if depth, no stencil
        /// desirability == 3000+        if depth and stencil
        /// beyond this, the total numer of bits (stencil+depth) is maximised
        if(props.modes[mode].stencil)
            desirability += 1000;
        if(props.modes[mode].depth)
            desirability += 2000;
        if(depthBits[props.modes[mode].depth] == 24) // Prefer 24 bit for now
            desirability += 500;
		if(depthFormats[props.modes[mode].depth] == GL_DEPTH24_STENCIL8_OES) // Prefer 24/8 packed 
			desirability += 5000;
        desirability += stencilBits[props.modes[mode].stencil] + depthBits[props.modes[mode].depth];
        
        if(desirability>bestscore)
        {
            bestscore = desirability;
            bestmode = mode;
        }
    }
    *depthFormat = depthFormats[props.modes[bestmode].depth];
    *stencilFormat = stencilFormats[props.modes[bestmode].stencil];
}

GLESFBORenderTexture *GLESFBOManager::createRenderTexture(const U2String &name, 
	const U2GLESSurfaceDesc &target, bool writeGamma, u2uint fsaa)
{
    GLESFBORenderTexture *retval = U2_NEW GLESFBORenderTexture(this, name, target, writeGamma, fsaa);
    return retval;
}

/**********************@@@@@@@@@@@@@@@@@@@@@
MultiRenderTarget *GLESFBOManager::createMultiRenderTarget(const U2String & name)
{
	return U2_NEW GLESFBOMultiRenderTarget(this, name);
}
*/

void GLESFBOManager::bind(U2RenderTarget *target)
{
    /// Check if the render target is in the rendertarget->FBO map
    GLESFrameBufferObject *fbo = 0;
    target->getCustomAttribute("FBO", &fbo);
    if(fbo)
        fbo->bind();
    else
        // Old style context (window/pbuffer) or copying render texture
#if U2_PLATFORM == U2_PLATFORM_APPLE_IOS
        // The screen buffer is 1 on iPhone
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, 1);
#else
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
#endif
    GL_CHECK_ERROR;
}

U2GLESSurfaceDesc GLESFBOManager::requestRenderBuffer(GLenum format, size_t width, size_t height, u2uint fsaa)
{
    U2GLESSurfaceDesc retval;
    retval.buffer = 0; // Return 0 buffer if GL_NONE is requested
    if(format != GL_NONE)
    {
        RBFormat key(format, width, height, fsaa);
        RenderBufferMap::iterator it = mRenderBufferMap.find(key);
        if(it != mRenderBufferMap.end())
        {
            retval.buffer = it->second.buffer;
            retval.zoffset = 0;
			retval.numSamples = fsaa;
            // Increase refcount
            ++it->second.refcount;
        }
        else
        {
            // New one
            GLESRenderBuffer *rb = U2_NEW GLESRenderBuffer(format, width, height, fsaa);
            mRenderBufferMap[key] = RBRef(rb);
            retval.buffer = rb;
            retval.zoffset = 0;
			retval.numSamples = fsaa;
        }
    }
//        std::cerr << "Requested renderbuffer with format " << std::hex << format << std::dec << " of " << width << "x" << height << " :" << retval.buffer << std::endl;
    return retval;
}
//-----------------------------------------------------------------------
void GLESFBOManager::requestRenderBuffer(const U2GLESSurfaceDesc &surface)
{
    if(surface.buffer == 0)
        return;
    RBFormat key(surface.buffer->getGLFormat(), surface.buffer->getWidth(), surface.buffer->getHeight(), surface.numSamples);
    RenderBufferMap::iterator it = mRenderBufferMap.find(key);
    assert(it != mRenderBufferMap.end());
    if (it != mRenderBufferMap.end())   // Just in case
    {
        assert(it->second.buffer == surface.buffer);
        // Increase refcount
        ++it->second.refcount;
    }
}
//-----------------------------------------------------------------------
void GLESFBOManager::releaseRenderBuffer(const U2GLESSurfaceDesc &surface)
{
    if(surface.buffer == 0)
        return;
    RBFormat key(surface.buffer->getGLFormat(), surface.buffer->getWidth(), surface.buffer->getHeight(), surface.numSamples);
    RenderBufferMap::iterator it = mRenderBufferMap.find(key);
    if(it != mRenderBufferMap.end())
	{
		// Decrease refcount
		--it->second.refcount;
		if(it->second.refcount==0)
		{
			// If refcount reaches zero, delete buffer and remove from map
			U2_DELETE it->second.buffer;
			mRenderBufferMap.erase(it);
			//std::cerr << "Destroyed renderbuffer of format " << std::hex << key.format << std::dec
			//        << " of " << key.width << "x" << key.height << std::endl;
		}
	}
}